package afcp.alumni.util;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import afcp.alumni.model.db.Member;

public class ReflectUtil {
	
//	public static List<Field> getFieldsWithAnnotation(Class<?> type, Class<? extends Annotation> annotationClass) {
//		List<Field> fields = new LinkedList<Field>();
//		for (Field field : getAllFields(Member.class)) {
//			if (field.isAnnotationPresent(annotationClass)) {
//				fields.add(field);
//			}
//		}
//		return fields;
//	}
	
	private static class Attribute {
		private String name;
		private Object value;
	}

	public static List<Field> getFieldsWithAnnotation(Class<?> type,
			Class<? extends Annotation> annotationClass) {
		return _getFieldsWithAnnotation(new LinkedList<Field>(), type,
				annotationClass, null);
	}
	
	public static List<Field> getFieldsWithAnnotation(Class<?> type,
	Class<? extends Annotation> annotationClass, String attrName, Object attrValue) {
		
		Attribute attr = new Attribute();
		attr.name = attrName;
		attr.value = attrValue;

		return _getFieldsWithAnnotation(new LinkedList<Field>(), type,
				annotationClass, attr);
	}

	private static List<Field> _getFieldsWithAnnotation(List<Field> fields,
			Class<?> type, Class<? extends Annotation> annotationClass, Attribute attr) {
		for (Field field : type.getDeclaredFields()) {
			if (field.isAnnotationPresent(annotationClass)) {
				if (attr == null) {
					fields.add(field);
				} else {
					Annotation annotation = field
							.getAnnotation(annotationClass);
					try {
						Object valueFound = annotationClass.getDeclaredMethod(
								attr.name).invoke(annotation);
						if (valueFound != null && valueFound.equals(attr.value)) {
							fields.add(field);
						}
					} catch (IllegalAccessException | IllegalArgumentException
							| InvocationTargetException | NoSuchMethodException
							| SecurityException e) {
					}
				}
			}
		}

		if (type.getSuperclass() != null) {
			fields = _getFieldsWithAnnotation(fields, type.getSuperclass(),
					annotationClass, attr);
		}

		return fields;
	}

	public static List<Field> getAllFields(Class<?> type) {
		return _getAllFields(new LinkedList<Field>(), type);
	}
	private static List<Field> _getAllFields(List<Field> fields, Class<?> type) {
		for (Field field : type.getDeclaredFields()) {
			fields.add(field);
		}

		if (type.getSuperclass() != null) {
			fields = _getAllFields(fields, type.getSuperclass());
		}

		return fields;
	}
	
	public static String findGetterName(Class<?> clazz, String name)
			throws IntrospectionException, NoSuchFieldException,
			NoSuchMethodException {
		return findGetter(clazz, name).getName();
	}

	public static Method findGetter(Class<?> clazz, String name)
			throws IntrospectionException, NoSuchFieldException,
			NoSuchMethodException {
		BeanInfo info = Introspector.getBeanInfo(clazz);
		for (PropertyDescriptor pd : info.getPropertyDescriptors())
			if (name.equals(pd.getName())) {
				Method method = pd.getReadMethod();
				if (method == null) {
					throw new NoSuchMethodException(clazz + " has no " + name
							+ " getter");
				}
				return method;
			}
		throw new NoSuchFieldException(clazz + " has no field " + name);
	}

	public static String findSetterName(Class<?> clazz, String name)
			throws IntrospectionException, NoSuchFieldException,
			NoSuchMethodException {
		return findSetter(clazz, name).getName();
	}

	public static Method findSetter(Class<?> clazz, String name)
			throws IntrospectionException, NoSuchFieldException,
			NoSuchMethodException {
		BeanInfo info = Introspector.getBeanInfo(clazz);
		for (PropertyDescriptor pd : info.getPropertyDescriptors())
			if (name.equals(pd.getName())) {
				Method method = pd.getWriteMethod();
				if (method == null) {
					throw new NoSuchMethodException(clazz + " has no " + name
							+ " setter");
				}
				return method;
			}
		throw new NoSuchFieldException(clazz + " has no field " + name);
	}
	
	public static Method[] findGetterSetter(Class<?> clazz, String name)
			throws IntrospectionException, NoSuchFieldException,
			NoSuchMethodException {
		BeanInfo info = Introspector.getBeanInfo(clazz);
		for (PropertyDescriptor pd : info.getPropertyDescriptors())
			if (name.equals(pd.getName())) {
				Method getter = pd.getReadMethod();
				Method setter = pd.getWriteMethod();
				if (getter == null) {
					throw new NoSuchMethodException(clazz + " has no " + name
							+ " getter");
				}
				if (setter == null) {
					throw new NoSuchMethodException(clazz + " has no " + name
							+ " setter");
				}
				return new Method[] { getter, setter };
			}
		throw new NoSuchFieldException(clazz + " has no field " + name);
	}
	
	
	private static final CopyFieldProcessor<Member> copyMember = new CopyFieldProcessor<>(
			Member.class);
	
	public static void copyMemberEditableFields(Member from, Member to) {
		copyMember.copyEditableFields(from, to);
	}
	
	public static <T> Field getField(T obj, String fieldName)
			throws NoSuchFieldException, SecurityException {
		Class<?> c = obj.getClass();
		return c.getDeclaredField(fieldName);
	}

	public static <T> void setFieldValue(T obj, String fieldName,
			Object newValue) throws NoSuchFieldException, SecurityException,
			IllegalArgumentException, IllegalAccessException {
		Field f = getField(obj, fieldName);
		setFieldValue(obj, f, newValue);
	}

	public static <T> void setFieldValue(T obj, Field field, Object newValue)
			throws IllegalArgumentException, IllegalAccessException {
		field.setAccessible(true);
		field.set(obj, newValue);
	}
	
	public static <T> Object getFieldValue(T obj, Field field)
			throws IllegalArgumentException, IllegalAccessException {
		field.setAccessible(true);
		return field.get(obj);
	}

}
